package pl.edu.mimuw.irs.core.model.petrinet;

import java.util.ArrayList;
import java.util.List;

import pl.edu.mimuw.irs.core.exception.InternalException;
import pl.edu.mimuw.irs.core.exception.InternalException.Cause;
import pl.edu.mimuw.irs.core.global.IDManager;
import pl.edu.mimuw.irs.core.util.RelativePosition;

/**
 * @author Jakub Rauch
 * Created on: 2009-05-07
 * 
 * Arcs are always directed (cannot have zero weight). 
 * If there are two arcs from X to Y, one of them must have negative weight, or both
 * are the same (the same instance). So there cannot be more than two different arcs
 * (one with negative, one with positive weight) between two nodes.
 */
public class Arc extends Element {
	
	private static final long serialVersionUID = 7893291312569246153L;

	// *** Properties
	
	public static final String VERTICES = "VERTICES";
	
	// *** Attributes
	
	// Arcs are always between Place and Transition. Weight indicates direction.
	private Transition transition;
	private Place place;
	// Weight of an arc.
	private int weight;
	// Direction of an arc.
	private Direction direction;
	// Vertices of this arc
	private List<RelativePosition> vertices;
	// Getters and setters
	public Transition getTransition() {
		return transition;
	}
	public void setTransition(Transition transition) {
		this.transition = transition;
	}
	public Place getPlace() {
		return place;
	}
	public void setPlace(Place place) {
		this.place = place;
	}
	public INode getSource() {
		switch (getDirection()) {
			case PLACE_TRANSITION:
				return getPlace();
			case TRANSITION_PLACE:
				return getTransition();
			default:
				throw new InternalException(Cause.PETRI_NET_UNKNOWN_ARC_DIRECTION);
		}
	}
	public INode getTarget() {
		switch (getDirection()) {
			case PLACE_TRANSITION:
				return getTransition();
			case TRANSITION_PLACE:
				return getPlace();
			default:
				throw new InternalException(Cause.PETRI_NET_UNKNOWN_ARC_DIRECTION);
		}
	}
	public int getWeight() {
		return weight;
	}
	public void setWeight(int weight) {
		this.weight = weight;
	}
	public Direction getDirection() {
		return direction;
	}
	public void setDirection(Direction direction) {
		this.direction = direction;
	}
	public List<RelativePosition> getVertices() {
		if (this.vertices == null) this.vertices = new ArrayList<RelativePosition>();
		return vertices;
	}
	public void setVertices(List<RelativePosition> vertices) {
		Object oldValue = this.vertices;
		this.vertices = vertices;
		getListeners().firePropertyChange(VERTICES, oldValue, vertices);
	}
	
	// *** Methods
	
	public Arc(IDManager idManager, String id, Place place, Transition transition, Direction direction, int weight) {
		if (weight < 0) throw new InternalException(Cause.PETRI_NET_NEGATIVE_WEIGHT_ARC);
		if (direction == null) throw new InternalException(Cause.PETRI_NET_UNKNOWN_ARC_DIRECTION);
		
		setIdManager(idManager);
		if (id == null) id = idManager.getNewId();
		setId(id);
		setPlace(place);
		setTransition(transition);
		setDirection(direction);
		setWeight(weight);
		
		place.addArc(this);
		transition.addArc(this);
	}
	
	public Arc(IDManager idManager, Place place, Transition transition, Direction direction, int weight) {
		this(idManager, null, place, transition, direction, weight);
	}
	
	public Arc(IDManager idManager, Place place, Transition transition, int weight) {
		this(idManager, place, transition, Direction.PLACE_TRANSITION, weight);
	}
	
	public Arc(IDManager idManager, Transition transition, Place place, int weight) {
		this(idManager, place, transition, Direction.TRANSITION_PLACE, weight);
	}
	
	public Arc(IDManager idManager, Place place, Transition transition) {
		this(idManager, place, transition, 1);
	}
	
	public Arc(IDManager idManager, Transition transition, Place place) {
		this(idManager, transition, place, 1);
	}
	
	public Arc(IDManager idManager, Place place) {
		this(idManager, place, null, 1);
	}
	
	public Arc(IDManager idManager, Transition transition) {
		this(idManager, transition, null, 1);
	}
	
	/* This constructor should never be used in a code - it is created only for
	 * JAXB serialization purposes.
	 */
	public Arc() {
		
	}
	
	/**
	 * Arc is not active when its source is a place and place does not contain
	 * as many tokens as required by this arc (weight). Otherwise it's active.
	 * @return
	 */
	public boolean isActive() {
		if (Direction.PLACE_TRANSITION.equals(getDirection()) &&
				getPlace().getTokens() < getWeight()) {
			return false;
		} else {
			return true;
		}
	}
	
	/**
	 * Executes this arc - depending on type removes or adds tokens to a place.
	 */
	public void invoke() {
		switch (getDirection()) {
			case PLACE_TRANSITION:
				//logger.trace("[" + this + "] :: " +
				//		"[" + getPlace() + " --(" + getWeight() + ")-> " + getTransition() + "]");
				getPlace().removeTokens(getWeight());
				break;
			case TRANSITION_PLACE:
				//logger.trace("[" + this + "] :: " +
				//		"[" + getTransition() + " --(" + getWeight() + ")-> " + getPlace() + "]");
				getPlace().addTokens(getWeight());
				break;
			default:
				throw new InternalException(Cause.PETRI_NET_UNKNOWN_ARC_DIRECTION);
		}
	}
	
	public void destroy() {
		Net net = getNet();
		Place place = getPlace();
		Transition transition = getTransition();
		setNet(null);
		setPlace(null);
		setTransition(null);
		if (net != null) net.removeArc(getId());
		if (place != null) place.removeArc(this);
		if (transition != null) transition.removeArc(this);
	}
	
	// *** Enums
	
	public enum Direction {
		TRANSITION_PLACE,
		PLACE_TRANSITION
	}
	
	@Override
	public String toString() {
		return super.toString() + "[" + getSource().getName() + " --> " + getTarget().getName() + "]";
	}
}
